/*
* Copyright 2015, RagingGoblin <http://raginggoblin.wordpress.com>
*
* This file is part of SpeechLess.
*
* SpeechLess is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SpeechLess is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with SpeechLess. If not, see <http://www.gnu.org/licenses/>.
*/
package raging.goblin.speechless.speech;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import marytts.LocalMaryInterface;
import marytts.MaryInterface;
import marytts.exceptions.MaryConfigurationException;
import marytts.exceptions.SynthesisException;
import marytts.util.data.audio.AudioPlayer;
import raging.goblin.speechless.Messages;
import raging.goblin.speechless.ui.ToastWindow;
@Slf4j
public class Speeker {
private static final Messages MESSAGES = Messages.getInstance();
private volatile boolean speeking;
private boolean isLastSentence;
private boolean isLastWord;
private AudioPlayer currentlySpeeking;
private Set<EndOfSpeechListener> endOfSpeechListeners = new HashSet<>();
private MaryInterface marytts;
public Speeker() throws MaryConfigurationException {
initMaryTTS();
}
public void stop() {
log.info("Stopping Speeker");
stopSpeeking();
}
public void stopSpeeking() {
speeking = false;
if (currentlySpeeking != null) {
currentlySpeeking.cancel();
}
}
public void speek(List<String> speeches) {
new Thread() {
@Override
public void run() {
speeking = true;
isLastSentence = false;
isLastWord = false;
for (int i = 0; i < speeches.size(); i++) {
if (i == speeches.size() - 1) {
isLastSentence = true;
}
if (speeking) {
speek(speeches.get(i).trim().toLowerCase());
}
}
};
}.start();
}
public void save(String speech, File file) {
new Thread("Saving speeches") {
@Override
public void run() {
if (!speech.trim().isEmpty()) {
ToastWindow toast = ToastWindow.showToast(MESSAGES.get("saving"), false);
try {
AudioInputStream audio = marytts.generateAudio(speech.toLowerCase());
AudioSystem.write(audio, AudioFileFormat.Type.WAVE, file);
toast.setVisible(false);
toast.dispose();
ToastWindow.showToast(MESSAGES.get("ready_saving"), true);
} catch (SynthesisException | IOException e) {
log.error("Unable to save speech", e);
toast.setVisible(false);
toast.dispose();
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(null,
MESSAGES.get("offending_speech"), MESSAGES.get("error"), JOptionPane.ERROR_MESSAGE));
}
}
}
}.start();
}
public void initMaryTTS() throws MaryConfigurationException {
log.debug("Initialiazing MaryTTS");
marytts = new LocalMaryInterface();
String voice = Voice.getSelectedVoice().getName();
log.debug("Setting voice to: " + voice);
marytts.setVoice(voice);
String effects = SoundEffect.toMaryTTSString();
log.debug("Setting effects to: " + effects);
marytts.setAudioEffects(effects);
}
public void addEndOfSpeechListener(EndOfSpeechListener listener) {
endOfSpeechListeners.add(listener);
}
private void speek(String speech) {
log.debug("Preparing to speek: " + speech);
try {
executeSpeaking(speech);
} catch (Exception e) {
log.error("Unable to speek: " + speech, e);
List<String> words = Arrays.asList(speech.split(" "));
if (words.size() > 1) {
speekWordsSeparately(words);
} else if (words.size() == 1) {
ToastWindow.showToast(String.format(MESSAGES.get("offending_word"), words.get(0)), true);
}
} finally {
if (readyWithSpeeking()) {
notifyEndOfSpeechListeners();
}
}
}
private void speekWordsSeparately(List<String> words) {
if (isLastSentence) {
isLastSentence = false;
for (int i = 0; i < words.size(); i++) {
if (i == words.size() - 1) {
isLastWord = true;
}
speek(words.get(i));
}
} else {
words.stream().forEach(w -> speek(w));
}
}
private void executeSpeaking(String speech) throws SynthesisException, InterruptedException {
AudioInputStream audio = marytts.generateAudio(speech);
currentlySpeeking = new AudioPlayer(audio);
log.debug("Speeking: " + speech);
currentlySpeeking.start();
currentlySpeeking.join();
}
private boolean readyWithSpeeking() {
return isLastSentence || isLastWord;
}
private void notifyEndOfSpeechListeners() {
endOfSpeechListeners.stream().forEach(l -> l.endOfSpeech());
}
public interface EndOfSpeechListener {
void endOfSpeech();
}
}